<?php

require __DIR__ . DIRECTORY_SEPARATOR . 'StoCodeTableBuilder.php';

/**
 * *基于*原有逻辑是使用网点编号匹配二段码,
 * 使用一段码是由于二段码的值取决于一段码(非固定)只是用于确定网点编号进而获取二段吗
 * 如果二段码是稳定的,可以忽略一段码的获取了
 */
class StoSecondCodeTableBuilder
{
    /**
     * branch_code also as key
     */
    const BRANCH_CODE_STRUCTURE = [
        'branch_code' => '',
        //网点编号,没啥用
        'branch_name' => '',
        //网点名，没啥用
        'second_code' => '',
        //二段码
        'province' => '',
        'city' => '',
        'district' => '',
        'multi_center' => 1,
        // 是否使用路由标识 === trans_centers.length
        'trans_centers' => [
            [
                'center_name' => '',
                // 没啥用
                'center_code' => '',
                // 获取一段码
                'pack_name' => '',
                //打包名 获取一段码
            ],
        ],
        'is_indie' => '',
        //独立与否, 这里没用,使用区县匹配有用
    ];

    /**
     *
     */
    const BRANCH_SIMPLE_CODE_STRUCTURE = [
        'branch_code' => [
            'second_code' => '',
            'multi_center' => 1,
            // 是否使用路由标识 === trans_centers.length
            'trans_centers' => [
                [
                    'center_code' => '',
                    // 获取一段码
                    'pack_name' => '',
                    //打包名 获取一段码
                ],
            ],
        ],
    ];

    /**
     * address level tree
     */
    const ADDRESS_BASE_CODE_STRUCTURE = [
        'province' => [
            'city' => [
                '_special__' => ['branch_code'],
                //独立
                'city' => [
                    self::BRANCH_SIMPLE_CODE_STRUCTURE,
                ],
                //branch_code array or just show useful district(filter branch much than 1 district)
            ],
        ],
    ];

    /**
     * 转运中心 hash key
     */

    const COLUMN_TRANS_CENTERS = 'trans_centers';

    const COLUMN_SECOND_CODE = 'second_code';
    const COLUMN_BRANCH_CODE = 'branch_code';
    const COLUMN_BRANCH_NAME = 'branch_name';
    const COLUMN_TRANS_PACK_NAME = 'trans_pack_name';
    const COLUMN_TRANS_CENTER_NAME = 'trans_center_name';
    const COLUMN_TRANS_CENTER_CODE = 'trans_center_code';

    const SERVICE_POINT_COUNT = 'service_point_count';

    const COLUMN_CENTER_FIELDS = [
        'center_name' => self::COLUMN_TRANS_CENTER_NAME,
        'center_code' => self::COLUMN_TRANS_CENTER_CODE,
        'pack_name' => self::COLUMN_TRANS_PACK_NAME,
    ];
    /**
     * address key
     */
    const ADDRESS_PROVINCE = 'province';
    const ADDRESS_CITY = 'city';
    const ADDRESS_DISTRICT = 'district';
    const ADDRESS_CITY_DOWNTOWN = '__DOWNTOWN__';
    /**
     * 独立网点
     */
    const ADDRESS_INDIE_ATTR_NAME = '__indie__';

    /**
     * branch code indexed hash table
     */
    const BRANCH_CODE_MAP = [
        self::COLUMN_BRANCH_CODE => 0,
        self::COLUMN_BRANCH_NAME => 1,
        self::COLUMN_TRANS_CENTER_NAME => [
            9,
            12,
            15,
        ],
        self::COLUMN_TRANS_CENTER_CODE => [
            10,
            13,
            16,
        ],
        self::COLUMN_TRANS_PACK_NAME => [
            11,
            14,
            17,
        ],
        self::COLUMN_SECOND_CODE => [
            7,
            8,
        ],
        //3+3
        'belong_to' => 2,
        self::ADDRESS_PROVINCE => 4,
        self::ADDRESS_CITY => 5,
        self::ADDRESS_DISTRICT => 6,
        'branch_type' => 3,
        //直属,加盟
    ];

    /**
     * format required column
     */
    const REQUIRED_FIELDS = [
        self::COLUMN_BRANCH_CODE,
        self::COLUMN_BRANCH_NAME,
        self::COLUMN_TRANS_CENTER_CODE,
        self::COLUMN_TRANS_PACK_NAME,
    ];

    /**
     *
     */
    const FILE_INFO_MAP = [
        0 => '网点编号',
        1 => '网点名称',
        2 => '所属网点',
        3 => '类型',
        4 => '省份',
        5 => '城市',
        6 => '区县',
        7 => '二段码编号',
        8 => '二段码编号2',
        9 => '第一转运中心',
        10 => '末端中心编码',
        11 => '打包名',
        12 => '第二转运中心',
        13 => '末端中心编码',
        14 => '打包名',
        15 => '第三转运中心',
        16 => '末端中心编码',
        17 => '打包名',
    ];

    /**
     *
     */
    const BRANCH_INDIE_SIGN = '独立网点';

    /**
     * @var string
     */
    private $fileSeparator = ',';
    /**
     * @var string
     */
    private $sourceFilePath;

    /**
     * 表里所有的数据
     * @var array
     */
    private $codeArray;

    /**
     * 基于 branch code的
     * @var array
     */
    private $codeBaseArray;

    /**
     * 基于地址树的
     * @var array
     */
    private $addressBaseArray;

    /**
     * 没有网点的数据
     * @var array
     */
    private $noServiceProviderArray;

    const FILE_NAME_CODE_BASE_ARRAY = 'code_base_array.php';
    const FILE_NAME_ADDRESS_BASE_ARRAY = 'address_base_array.php';

    const FILE_CONTENT_PREFIX = <<<ARRAY
<?php\r\n
return\t
ARRAY;

    /**
     * @var array
     */
    protected $codeColumns;

    /**
     * columns count
     * @var int
     */
    private $columnLength;

    /**
     * @var array
     */
    private $lastFormattedRow;

    /**
     * @var array
     */
    private $lastFormattedRowInfo;

    /**
     * @var integer
     */
    private $currentItemIndex;

    /**
     * StoSecondCodeTableBuilder constructor.
     * @param string $filePath
     * @param string $fileSeparator
     */
    public function __construct(string $filePath, string $fileSeparator = ',')
    {
        if (!file_exists($filePath) || !is_readable($filePath)) {
            die(sprintf('file: %s not exists or unreadable', $filePath));
        }
        $this->sourceFilePath = $filePath;
        $fileSeparator and $this->fileSeparator = $fileSeparator;
    }

    /**
     * script entry
     */
    public function run()
    {
        $this->init();
        $this->build();
        $this->toFile();
    }

    /**
     * @param array $data
     * @return bool
     */
    protected function checkRequiredField(array $data): bool
    {
        foreach (static::REQUIRED_FIELDS as $field) {
            if (!isset($data[$field]) || (!is_array($data[$field]) && !trim($data[$field])) || (is_array($data[$field]) && !count($data[$field]))) {
                $this->printErrorInfo($data, 'missing required params:');
                return false;
            }
        }
        return true;
    }

    /**
     * @param array $data
     * @return array
     */
    private function filterFormattedData(array $data): array
    {
        return array_intersect_key($data, static::BRANCH_CODE_STRUCTURE);
    }

    /**
     * @param array $data
     * @return array
     */
    private function formatInfo(array $data): array
    {
        $structureData = [];
        foreach (static::BRANCH_CODE_MAP as $column => $item) {
            if (is_array($item)) {
                foreach ($item as $k => $index) {
                    trim($data[$index]) and $structureData[$column][$k] = $data[$index];
                }
            } else {
                $structureData[$column] = $data[$item];
            }
        }
        $this->lastFormattedRowInfo = $structureData;
        if (!$this->checkRequiredField($structureData)) {
            $this->noServiceProviderArray[] = $structureData;
            return [];
        }
        $this->combineCenterInfo($structureData);
        $this->combineSecondCode($structureData);
        $structureData['multi_center'] = $this->isMultiCenterBranch($structureData);
        $structureData['is_indie'] = $this->isIndieBranch($structureData['branch_type']);
        return $this->lastFormattedRow = $this->filterFormattedData($structureData);
    }

    /**
     * @param array $data
     * @return array
     */
    private function combineCenterInfo(array &$data): array
    {
        $keys = $data[static::COLUMN_TRANS_CENTER_CODE];
        $values = [];
        for ($i = 0, $n = count($keys); $i < $n; $i++) {
            foreach (static::COLUMN_CENTER_FIELDS as $key => $field) {
                $values[$i][$key] = $data[$field][$i];
            }
        }
        foreach (static::COLUMN_CENTER_FIELDS as $field) {
            unset($data[$field]);
        }
        $data[static::COLUMN_TRANS_CENTERS] = array_combine($keys, $values);
        return $data;
    }

    /**
     * @param array $data
     * @return array
     */
    private function combineSecondCode(array &$data): array
    {
        if (!empty($data[static::COLUMN_SECOND_CODE]) && is_array($data[static::COLUMN_SECOND_CODE])) {
            $data[static::COLUMN_SECOND_CODE] = implode('', $data[static::COLUMN_SECOND_CODE]);
        }
        return $data;
    }

    /**
     * init data
     */
    private function init()
    {
        $this->toArray();
        $this->findInfoPosition($this->codeColumns);
    }

    /**
     * @param array $data
     * @return int
     */
    private function isMultiCenterBranch(array $data): int
    {
        return count($data[static::COLUMN_TRANS_CENTERS]);
    }

    /**
     * @param string $str
     * @return bool
     */
    private function isIndieBranch(string $str): bool
    {
        return $str === static::BRANCH_INDIE_SIGN;
    }
    /**
     * @return array
     */
    protected function toFile(): array
    {
        $result = [];
        if ($this->codeBaseArray && is_array($this->codeBaseArray)) {
            $result[] = file_put_contents(static::FILE_NAME_CODE_BASE_ARRAY, static::FILE_CONTENT_PREFIX . var_export($this->codeBaseArray, true) . ';');
        }
        if ($this->addressBaseArray && is_array($this->addressBaseArray)) {
            $result[] = file_put_contents(static::FILE_NAME_ADDRESS_BASE_ARRAY, static::FILE_CONTENT_PREFIX . var_export($this->addressBaseArray, true) . ';');
        }
        return $result;
    }

    /**
     * build info
     */
    protected function build()
    {
        $this->buildCodeBase();
        $this->buildAddressBase();
    }

    /**
     * @return array
     */
    private function buildCodeBase(): array
    {
        if ($this->codeBaseArray === null) {
            $data = [];
            foreach ($this->codeArray as $key => $item) {
                $this->currentItemIndex = $key;
                $tmp = $this->formatInfo($item);
                $tmpKey = !empty($tmp[static::COLUMN_BRANCH_CODE]) ? $tmp[static::COLUMN_BRANCH_CODE] : $key;
                $data[$tmpKey] = $tmp;
                if (empty($tmp[static::COLUMN_BRANCH_CODE])) {
                    unset($data[$tmpKey]);
                    continue;
                }
            }
            $this->codeBaseArray = $data;
        }
        return $this->codeBaseArray;
    }

    /**
     * @return array
     * @todo 确定是否需要收录无效的地址？？
     */
    private function buildAddressBase(): array
    {
        if ($this->addressBaseArray === null) {
            $data = [];
            $keyTpl = '%s_%s_%s';
            foreach ($this->codeBaseArray as $item) {
                if (empty($item[static::ADDRESS_PROVINCE]) || empty($item[static::ADDRESS_CITY])) {
                    $this->printErrorInfo($item);
                    continue;
                }
                $filteredItem = $this->filterAddressBaseData($item);

                if (empty($item[static::ADDRESS_DISTRICT])) {
                    $data[sprintf($keyTpl, $item[static::ADDRESS_PROVINCE], $item[static::ADDRESS_CITY], static::ADDRESS_CITY_DOWNTOWN)][$item[static::COLUMN_BRANCH_CODE]] = $filteredItem;
                    //$data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][static::ADDRESS_CITY_DOWNTOWN][$item[static::COLUMN_BRANCH_CODE]] = $filteredItem;
                    //                    isset($data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][static::ADDRESS_CITY_DOWNTOWN][static::SERVICE_POINT_COUNT]) and $data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][static::ADDRESS_CITY_DOWNTOWN][static::SERVICE_POINT_COUNT]++ or $data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][static::ADDRESS_CITY_DOWNTOWN][static::SERVICE_POINT_COUNT] = 1;
                    //district level also could have indie branch
                    if ($item['is_indie'] && substr($item[static::COLUMN_SECOND_CODE], -3) === '000') {
                        // $data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][static::ADDRESS_INDIE_ATTR_NAME] = $filteredItem;
                        $data[sprintf($keyTpl, $item[static::ADDRESS_PROVINCE], $item[static::ADDRESS_CITY], static::ADDRESS_INDIE_ATTR_NAME)] = $filteredItem;
                    }
                } else {
                    $data[sprintf($keyTpl, $item[static::ADDRESS_PROVINCE], $item[static::ADDRESS_CITY], $item[static::ADDRESS_DISTRICT])][$item[static::COLUMN_BRANCH_CODE]] = $filteredItem;
                    // $item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][$item[static::ADDRESS_DISTRICT]][$item[static::COLUMN_BRANCH_CODE]] = $filteredItem;
                    //$data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][$item[static::ADDRESS_DISTRICT]][$item[static::COLUMN_BRANCH_CODE]] = $filteredItem;
                    //                    isset($data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][$item[static::ADDRESS_DISTRICT]][static::SERVICE_POINT_COUNT]) and $data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][$item[static::ADDRESS_DISTRICT]][static::SERVICE_POINT_COUNT]++ or $data[$item[static::ADDRESS_PROVINCE]][$item[static::ADDRESS_CITY]][$item[static::ADDRESS_DISTRICT]][static::SERVICE_POINT_COUNT] = 1;
                }
            }
            $this->addressBaseArray = $data;
        }
        return $this->addressBaseArray;
    }

    /**
     * @param array $data
     */
    private function printErrorInfo(array $data, string $msg = 'error happened')
    {
        echo 'index:[' . $this->currentItemIndex, '];', $msg, json_encode($data, JSON_UNESCAPED_UNICODE), json_encode($this->lastFormattedRowInfo, JSON_UNESCAPED_UNICODE), PHP_EOL;
    }

    /**
     * @param array $data
     * @return array
     */
    private function filterAddressBaseData(array $data): array
    {
        return array_diff_key($data, array_flip([
            static::ADDRESS_PROVINCE,
            static::ADDRESS_CITY,
            static::ADDRESS_DISTRICT,
        ]));
    }

    /**
     * @param array $data
     * @return array
     */
    private function findInfoPosition(array $data): array
    {
        return $data;
    }

    /**
     * file to array
     * @return array
     */
    protected function toArray(): array
    {
        if ($this->codeArray === null) {
            $fileHandler = fopen($this->sourceFilePath, 'rb');
            $arr = [];
            while (!feof($fileHandler)) {
                if (!$line = fgets($fileHandler)) {
                    continue;
                }
                $arr[] = explode($this->fileSeparator, $line);
            }
            fclose($fileHandler);
            $this->codeColumns = array_map('trim', array_shift($arr));
            $this->columnLength = count($this->codeColumns);
            $this->codeArray = $arr;
        }
        return $this->codeArray;
    }
}

$filePath = __DIR__ . DIRECTORY_SEPARATOR . '20170116_2nd_code.csv';
(new StoSecondCodeTableBuilder($filePath))->run();
